/************************************************************************
                                                                     
   Xceed Ultimate ListBox for Silverlight                                                                                                                                            
   Copyright (C) 2010 Xceed Software Inc.    
                                                                     
   This program is provided to you under the terms of the GNU General Public  
   License version 2 as published by the Free Software Foundation. 
        
   This program is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY, without even the implied warranty of 
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public License along 
   with this program, if not, write to the Free Software Foundation, Inc., 
   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

   This program can be provided to you by Xceed Software Inc. under a
   proprietary commercial license agreement for use in non-Open Source
   projects. Visit Xceed.com to find the commercial edition of 
   Xceed Ultimate ListBox for Silverlight.                                    
                                                                      
 **********************************************************************/

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Markup;
using System.Windows.Media;
using System.Windows.Threading;
using Xceed.Silverlight.Data;
using Xceed.Silverlight.ListBox.Utils;
using Xceed.Silverlight.Utils;
using Xceed.Silverlight.Controls;
using Xceed.Silverlight.DragDrop;

namespace Xceed.Silverlight.ListBox
{
  [TemplateVisualState( GroupName = "CommonStates", Name = "Disabled" )]
  [TemplateVisualState( GroupName = "CommonStates", Name = "Normal" )]
  [TemplateVisualState( GroupName = "ResquestStatus", Name = "RequestsPending" )]
  [TemplateVisualState( GroupName = "ResquestStatus", Name = "NoPendingRequests" )]
  [TemplateVisualState( GroupName = "LicensingStates", Name = "Licensed" )]
  [TemplateVisualState( GroupName = "LicensingStates", Name = "NotLicensed" )]
  [TemplateVisualState( GroupName = "DataNavigationPaneStates", Name = "DataNavigationPaneVisible" )]
  [TemplateVisualState( GroupName = "DataNavigationPaneStates", Name = "DataNavigationPaneHidden" )]
  [TemplatePart( Name = "ScrollViewer", Type = typeof( ScrollViewer ) )]
  [ContentProperty( "Items" )]
  public partial class ListBox : Control,
    INotifyPropertyChanged,
    ISupportInitialize,
    IWeakEventListener,
    IDataNavigationHost,
    IDropTarget
  {
    #region STATIC MEMBERS

    private const string DisplayMemberTemplateXAML =
      @"<DataTemplate xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation"">
      <ContentPresenter Content=""{Binding %PATH%}""/>
      </DataTemplate>";

    private static ContainerConfigurations DefaultContainerConfigurations
    {
      get
      {
        if( DefaultContainerConfigurationsCache == null )
        {
          DefaultContainerConfigurationsCache = new ContainerConfigurations();
        }

        return DefaultContainerConfigurationsCache;
      }
    }

    private static ContainerConfigurations DefaultContainerConfigurationsCache;

    #endregion STATIC MEMBERS

    #region Constructors

    static ListBox()
    {
#pragma warning disable 219
      // To ensure the Xceed.Silverlight.Data.Controls reference is correctly
      // included in the Xceed.Silverlight.ListBox, we must at least reference
      // an assembly member in code behind. 
      // If we don't, even if the assembly is used in xaml, this reference is ignored.
      Func<FilterMode> func = () => FilterMode.And;
#pragma warning restore 219
    }

    public ListBox()
    {
      // Some features must be disabled in design mode.
      m_isInDesigner = System.ComponentModel.DesignerProperties.IsInDesignTool
                || Xceed.Utils.Licensing.DesignerProperties.GetIsInDesignMode()
                || System.ComponentModel.DesignerProperties.GetIsInDesignMode( this );

      // Lock the scrolling if the ListBox is in designer mode
      this.IsLocked = m_isInDesigner;



      this.DefaultStyleKey = typeof( ListBox );

      // While we have no ItemsSource, we consider ourselves Unbound.
      this.IsUnbound = true;

      m_items = new ItemsWrapper( this );

      m_selectionManager = new SelectionManager( this );
      m_selectionManager.SelectionChanged += this.OnSelectionManagerSelectionChanged;
      m_selectionManager.UpdateSelectionProperties += this.OnSelectionManagerUpdateSelectionProperties;

      m_selectedItems = new SelectedItemsWrapper( this );
      m_selectedItems.Completed += this.OnSelectedItemsCompleted;
      ObservableCollection<SelectionRange> selectedRanges = new ObservableCollection<SelectionRange>();
      selectedRanges.CollectionChanged += this.OnSelectedRangesChanged;
      this.SelectedRanges = selectedRanges;

      Binding binding = new Binding();
      binding.RelativeSource = new RelativeSource( RelativeSourceMode.Self );
      binding.Path = new PropertyPath( "AllowDrop" );
      this.SetBinding( ListBox.InternalAllowDropProperty, binding );

      this.Loaded += this.OnLoaded;
      this.Unloaded += this.OnUnLoaded;
      this.IsEnabledChanged += this.OnIsEnabledChanged;
    }

    #endregion Constructors

    #region FilterExpression Property

    public static readonly DependencyProperty FilterExpressionProperty = DependencyProperty.Register(
      "FilterExpression",
      typeof( FilterExpressionBase ),
      typeof( ListBox ),
      new PropertyMetadata(
        null,
        new PropertyChangedCallback( ListBox.OnFilterExpressionChanged ) ) );

    public FilterExpressionBase FilterExpression
    {
      get
      {
        return ( FilterExpressionBase )this.GetValue( ListBox.FilterExpressionProperty );
      }
      set
      {
        this.SetValue( ListBox.FilterExpressionProperty, value );
      }
    }

    private static void OnFilterExpressionChanged( DependencyObject sender, DependencyPropertyChangedEventArgs e )
    {
      ListBox listBox = sender as ListBox;

      // Never apply Grouping, Sorting, or Filtering to an unbound listbox.
      // Else the list is untyped and the data source wrapper won't be able to find
      // the properties corresponding to the grouping, sorting, or filtering criterias
      // resulting in a crash.
      if( ( listBox == null ) || ( !listBox.IsInitialized ) || ( listBox.IsUnbound ) )
        return;

      listBox.Reset();
    }

    #endregion

    #region Filter Event

    public event EventHandler<ListBoxFilterEventArgs> Filter
    {
      add
      {
        m_filter = this.GeneratePredicateForFilter( value );
        this.ResetOnFilterPredicateChanged();
      }
      remove
      {
        m_filter = null;
        this.ResetOnFilterPredicateChanged();
      }
    }

    private void ResetOnFilterPredicateChanged()
    {
      if( !this.IsInitialized )
        return;

      this.ClearDataSourceEnumeratorWrapper();

      this.UpdateSelectedItemAndSelectedItems();

      this.DeferRefresh();
    }

    private Predicate<object> GeneratePredicateForFilter( EventHandler<ListBoxFilterEventArgs> handler )
    {
      if( handler == null )
        return null;

      return new Predicate<object>( ( item ) =>
      {
        Debug.Assert( !( item is DataItem ), "A DataItem was the item passed as a parameter." );

        ListBoxFilterEventArgs eventArgs = new ListBoxFilterEventArgs( item );

        handler.Invoke( this, eventArgs );

        return eventArgs.Accepted;
      } );
    }

    #endregion

    #region DisplayMemberPath Property

    public static readonly DependencyProperty DisplayMemberPathProperty = DependencyProperty.Register(
      "DisplayMemberPath",
      typeof( string ),
      typeof( ListBox ),
      new PropertyMetadata( null, new PropertyChangedCallback( ListBox.OnDisplayMemberPathChanged ) ) );

    public string DisplayMemberPath
    {
      get
      {
        return ( string )this.GetValue( ListBox.DisplayMemberPathProperty );
      }
      set
      {
        this.SetValue( ListBox.DisplayMemberPathProperty, value );
      }
    }

    private static void OnDisplayMemberPathChanged( DependencyObject sender, DependencyPropertyChangedEventArgs e )
    {
      ListBox listBox = sender as ListBox;

      if( listBox == null )
        return;

      if( listBox.ItemTemplate != null )
        throw new InvalidOperationException( "Cannot set ItemTemplate and DisplayMemberPath simultaneously." );

      listBox.InvalidateContainerConfigurationCache( ConfigurationType.Item );
    }

    #endregion DisplayMemberPath Property

    #region IsUnbound Internal Property

    internal bool IsUnbound
    {
      get;
      set;
    }

    #endregion

    #region ItemContainerStyle Property

    public static readonly DependencyProperty ItemContainerStyleProperty = DependencyProperty.Register(
      "ItemContainerStyle",
      typeof( Style ),
      typeof( ListBox ),
      new PropertyMetadata(
        null,
        new PropertyChangedCallback( ListBox.OnItemContainerStyleChanged ) ) );

    public Style ItemContainerStyle
    {
      get
      {
        return ( Style )this.GetValue( ListBox.ItemContainerStyleProperty );
      }
      set
      {
        this.SetValue( ListBox.ItemContainerStyleProperty, value );
      }
    }

    private static void OnItemContainerStyleChanged( DependencyObject sender, DependencyPropertyChangedEventArgs e )
    {
      ListBox listBox = sender as ListBox;

      if( listBox == null )
        return;

      listBox.InvalidateContainerConfigurationCache( ConfigurationType.Item );
    }

    #endregion ItemContainerStyle Property

    #region Items Property

    public IList Items
    {
      get
      {
        Debug.Assert( m_items != null );
        return m_items;
      }
    }

    #endregion Items Property

    #region ItemsPanel Property

    public static readonly DependencyProperty ItemsPanelProperty = DependencyProperty.Register(
      "ItemsPanel",
      typeof( ItemsPanelTemplate ),
      typeof( ListBox ),
      new PropertyMetadata( null ) );

    public ItemsPanelTemplate ItemsPanel
    {
      get
      {
        return ( ItemsPanelTemplate )this.GetValue( ListBox.ItemsPanelProperty );
      }
      set
      {
        this.SetValue( ListBox.ItemsPanelProperty, value );
      }
    }

    #endregion ItemsPanel Property

    #region ItemsSource Property

    public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register(
      "ItemsSource",
      typeof( object ),
      typeof( ListBox ),
      new PropertyMetadata(
        null,
        new PropertyChangedCallback( ListBox.OnItemsSourceChanged ) ) );

    public object ItemsSource
    {
      get
      {
        return this.GetValue( ListBox.ItemsSourceProperty );
      }
      set
      {
        this.SetValue( ListBox.ItemsSourceProperty, value );
      }
    }

    private static void OnItemsSourceChanged( DependencyObject sender, DependencyPropertyChangedEventArgs e )
    {
      ListBox listBox = sender as ListBox;

      if( listBox == null )
        return;

      listBox.Reset();
    }

    #endregion ItemsSource Property

    #region ItemTemplate Property

    public static readonly DependencyProperty ItemTemplateProperty = DependencyProperty.Register(
      "ItemTemplate",
      typeof( DataTemplate ),
      typeof( ListBox ),
      new PropertyMetadata( null, new PropertyChangedCallback( ListBox.OnItemTemplateChanged ) ) );

    public DataTemplate ItemTemplate
    {
      get
      {
        return ( DataTemplate )this.GetValue( ListBox.ItemTemplateProperty );
      }
      set
      {
        this.SetValue( ListBox.ItemTemplateProperty, value );
      }
    }

    private static void OnItemTemplateChanged( DependencyObject sender, DependencyPropertyChangedEventArgs e )
    {
      ListBox listBox = sender as ListBox;

      if( listBox == null )
        return;

      if( !String.IsNullOrEmpty( listBox.DisplayMemberPath ) )
        throw new InvalidOperationException( "Cannot set ItemTemplate and DisplayMemberPath simultaneously." );

      listBox.InvalidateContainerConfigurationCache( ConfigurationType.Item );

      listBox.OnItemTemplateChanged( e.OldValue as DataTemplate, e.NewValue as DataTemplate );
    }

    #endregion ItemTemplate Property

    #region ContainerConfigurations Property

    public static readonly DependencyProperty ContainerConfigurationsProperty = DependencyProperty.Register(
       "ContainerConfigurations",
       typeof( ContainerConfigurations ),
       typeof( ListBox ),
       new PropertyMetadata( new ContainerConfigurations(), new PropertyChangedCallback( ListBox.OnContainerConfigurationsChanged ) ) );

    public ContainerConfigurations ContainerConfigurations
    {
      get
      {
        return ( ContainerConfigurations )this.GetValue( ListBox.ContainerConfigurationsProperty );
      }
      set
      {
        this.SetValue( ListBox.ContainerConfigurationsProperty, value );
      }
    }

    private static void OnContainerConfigurationsChanged( DependencyObject sender, DependencyPropertyChangedEventArgs e )
    {
      ListBox listBox = ( ListBox )sender;

      ContainerConfigurations oldContainerConfigurations = e.OldValue as ContainerConfigurations;
      ContainerConfigurations newContainerConfigurations = e.NewValue as ContainerConfigurations;

      if( oldContainerConfigurations != null )
      {
        ContainerConfigurationsInvalidatedManager.RemoveListener( oldContainerConfigurations, listBox );
      }

      if( newContainerConfigurations != null )
      {
        ContainerConfigurationsInvalidatedManager.AddListener( newContainerConfigurations, listBox );
      }

      // Invalidating the ContainerConfigurations also clears automatically the
      // Container Configuration, Container ContentTemplate, and Container Style for every thing.
      listBox.ClearContainerConfigurationCache();

    }

    #endregion ContainerConfigurations Property

    #region IsAsyncDataSource Internal Read-Only Property

    internal bool IsAsyncDataSource
    {
      get;
      private set;
    }

    #endregion

    #region GroupDescriptions Property

    public ObservableCollection<GroupDescription> GroupDescriptions
    {
      get
      {
        if( m_groupDescriptions == null )
        {
          m_groupDescriptions = new ObservableCollection<GroupDescription>();
          m_groupDescriptions.CollectionChanged += this.OnGroupDescriptions_CollectionChanged;
        }

        return m_groupDescriptions;
      }
    }

    private void OnGroupDescriptions_CollectionChanged( object sender, NotifyCollectionChangedEventArgs e )
    {
      // Never apply Grouping, Sorting, or Filtering to an unbound listbox.
      // Else the list is untyped and the data source wrapper won't be able to find
      // the properties corresponding to the grouping, sorting, or filtering criterias
      // resulting in a crash.
      if( ( !this.IsInitialized ) || ( this.IsUnbound ) )
        return;

      this.UpdateDataSourceWrapperGroupDescriptions();
      this.DeferRefresh();
    }

    private ObservableCollection<GroupDescription> m_groupDescriptions;

    #endregion

    #region SortDescriptions Property

    public ObservableCollection<SortDescription> SortDescriptions
    {
      get
      {
        if( m_sortDescriptions == null )
        {
          m_sortDescriptions = new ObservableCollection<SortDescription>();
          m_sortDescriptions.CollectionChanged += this.OnSortDescriptions_CollectionChanged;
        }

        return m_sortDescriptions;
      }
    }

    private void OnSortDescriptions_CollectionChanged( object sender, NotifyCollectionChangedEventArgs e )
    {
      // Never apply Grouping, Sorting, or Filtering to an unbound listbox.
      // Else the list is untyped and the data source wrapper won't be able to find
      // the properties corresponding to the grouping, sorting, or filtering criterias
      // resulting in a crash.
      if( ( !this.IsInitialized ) || ( this.IsUnbound ) )
        return;

      this.UpdateDataSourceWrapperSortDescriptions();
      this.DeferRefresh();
    }

    private ObservableCollection<SortDescription> m_sortDescriptions;

    #endregion

    #region ListBoxAnimationSettings Property

    public static readonly DependencyProperty AnimationSettingsProperty = DependencyProperty.Register(
      "AnimationSettings",
      typeof( ListBoxAnimationSettings ),
      typeof( ListBox ),
      new PropertyMetadata( new ListBoxAnimationSettings() ) );

    public ListBoxAnimationSettings AnimationSettings
    {
      get
      {
        return ( ListBoxAnimationSettings )this.GetValue( ListBox.AnimationSettingsProperty );
      }
      set
      {
        this.SetValue( ListBox.AnimationSettingsProperty, value );
      }
    }

    #endregion

    #region Panel Internal Property

    internal PanelBase Panel
    {
      get
      {
        return m_panel;
      }
      private set
      {
        if( m_panel != value )
        {
          m_panel = value;
          this.OnPanelChanged();
        }
      }
    }

    private PanelBase m_panel;

    internal void OnPanelChanged()
    {
      if( m_previousView != null )
        m_previousView.RaiseCanExecuteChanged();

      if( m_nextView != null )
        m_nextView.RaiseCanExecuteChanged();
    }

    #endregion

    #region CurrentDataPath Internal Property

    internal DataPath CurrentDataPath
    {
      get;
      set;
    }

    #endregion

    #region IsUpdateSelectedItemsAndItemInhibited
    internal bool IsUpdateSelectedItemsAndItemInhibited
    {
      get
      {
        return ( m_inhibitUpdateSelectedItemAndItemsCount > 0 );
      }
    }
    #endregion

    #region SelectedItem Property

    public static readonly DependencyProperty SelectedItemProperty = DependencyProperty.Register(
      "SelectedItem",
      typeof( object ),
      typeof( ListBox ),
      new PropertyMetadata(
        null,
        new PropertyChangedCallback( ListBox.OnSelectedItemChanged ) ) );

    public object SelectedItem
    {
      get
      {
        return ( object )this.GetValue( ListBox.SelectedItemProperty );
      }
      set
      {
        this.SetValue( ListBox.SelectedItemProperty, value );
      }
    }

    private static void OnSelectedItemChanged( DependencyObject sender, DependencyPropertyChangedEventArgs e )
    {
      ListBox listBox = sender as ListBox;

      if( listBox == null )
        return;

      if( !listBox.m_updatingSelectedItemFromSelectedValue )
      {
        listBox.UpdateSelectedValueFromSelectedItem();
      }

      listBox.UpdateSelectedRangesFromSelectedItem();
    }

    private void UpdateSelectedValueFromSelectedItem()
    {
      if( String.IsNullOrEmpty( m_cachedSelectedValuePath ) )
        return;

      Debug.Assert( !m_updatingSelectedValueFromSelectedItem );
      m_updatingSelectedValueFromSelectedItem = true;
      try
      {
        object selectedItem = this.SelectedItem;

        if( selectedItem == null )
        {
          this.SelectedValue = null;
          return;
        }

        this.SelectedValue = this.SelectedValuePathExtractor.GetValueFromItem( selectedItem );
      }
      finally
      {
        m_updatingSelectedValueFromSelectedItem = false;
      }
    }

    #endregion

    #region SelectedItems Property

    public IList SelectedItems
    {
      get
      {
        Debug.Assert( m_selectedItems != null );
        return m_selectedItems;
      }
    }

    #endregion

    #region SelectionManager Internal Property

    internal SelectionManager SelectionManager
    {
      get
      {
        return m_selectionManager;
      }
    }

    #endregion

    #region SelectionMode Property

    public static readonly DependencyProperty SelectionModeProperty = DependencyProperty.Register(
      "SelectionMode",
      typeof( SelectionMode ),
      typeof( ListBox ),
      new PropertyMetadata(
        SelectionMode.Single,
        new PropertyChangedCallback( ListBox.OnSelectionModeChanged ) ) );

    public SelectionMode SelectionMode
    {
      get
      {
        return ( SelectionMode )this.GetValue( ListBox.SelectionModeProperty );
      }
      set
      {
        this.SetValue( ListBox.SelectionModeProperty, value );
      }
    }

    private static void OnSelectionModeChanged( DependencyObject sender, DependencyPropertyChangedEventArgs e )
    {
      ListBox listBox = sender as ListBox;

      if( listBox == null )
        return;

      listBox.m_selectionManager.SelectionMode = ( SelectionMode )e.NewValue;
    }

    #endregion

    #region SelectedRanges Property

    public IList<SelectionRange> SelectedRanges
    {
      get;
      private set;
    }

    private void OnSelectedRangesChanged( object sender, NotifyCollectionChangedEventArgs e )
    {
      IList selectedRanges = sender as IList;

      switch( e.Action )
      {

        case NotifyCollectionChangedAction.Add:
        case NotifyCollectionChangedAction.Replace:
          this.HandleSelectionRangeAddition( e.NewItems );
          break;

        case NotifyCollectionChangedAction.Reset:

          if( selectedRanges.Count > 0 )
          {
            m_selectionManager.UnselectAllHelper();
          }
          else
          {
            // We must update the VisualState of every containers
            // in the Panel since no more SelectionRange are
            // present in the SelectedRanges
            PanelBase panel = this.Panel;

            if( panel != null )
              panel.UpdateSelectionPropertiesOnContainers();

            return;
          }
          break;

        case NotifyCollectionChangedAction.Remove:
        default:
          break;
      }

      if( !m_selectionManager.IsUpdatingSelection )
      {
        m_selectionManager.NotifySelectionChanged();
      }
    }

    private void HandleSelectionRangeAddition( IList addedSelectionRanges )
    {
      DataSourceWrapper dataSource = this.DataSourceWrapper;

      Debug.Assert( dataSource != null );

      if( dataSource == null )
        return;

      foreach( SelectionRange selectionRange in addedSelectionRanges )
      {
        if( selectionRange.UseDefaultDataRangeContext )
        {
          selectionRange.DataRangeContext = dataSource.GetCurrentDataRangeContext();
        }
        else
        {
          IEnumerable<SortDescription> sortDescriptions = selectionRange.SortDescriptions;
          if( selectionRange.SortDescriptions.Count == 0 )
          {
            sortDescriptions = dataSource.AppliedSortDescriptions;
          }

          selectionRange.DataRangeContext = new DataRangeContext( dataSource.ElementType, sortDescriptions, selectionRange.Filter, selectionRange.FilterExpression );
        }
      }
    }

    #endregion

    #region SelectedValue Property

    public static readonly DependencyProperty SelectedValueProperty =
        DependencyProperty.Register( "SelectedValue", typeof( object ), typeof( ListBox ),
        new PropertyMetadata( null, new PropertyChangedCallback( ListBox.OnSelectedValueChanged ) ) );

    public object SelectedValue
    {
      get { return this.GetValue( ListBox.SelectedValueProperty ); }
      set { this.SetValue( ListBox.SelectedValueProperty, value ); }
    }

    private static void OnSelectedValueChanged( object sender, DependencyPropertyChangedEventArgs e )
    {
      ListBox listBox = ( ListBox )sender;

      if( !listBox.m_updatingSelectedValueFromSelectedItem )
      {
        // The change does not come from an explicit call to update the SelectedValue from the SelectedItem.
        // We must update the SelectedItem from the new SelectedValue.
        listBox.UpdateSelectedItemFromSelectedValue();
      }
    }

    private void UpdateSelectedItemFromSelectedValue()
    {
      Debug.Assert( !m_updatingSelectedItemFromSelectedValue );
      m_updatingSelectedItemFromSelectedValue = true;
      try
      {
        if( this.IsAsyncDataSource )
          throw new NotSupportedException( "Setting SelectedValue is not supported while bound to an asynchronous source." );

        object selectedValue = this.SelectedValue;

        if( selectedValue == null )
        {
          this.SelectedItem = null;
          return;
        }

        BindingPathValueExtractor selectedValuePathExtractor = this.SelectedValuePathExtractor;

        // Loop in the items to find a match.
        DataSourceEnumeratorWrapper syncEnumerator = m_dataSourceWrapper.GetSyncDataEnumerator();
        while( syncEnumerator.MoveNext() )
        {
          DataPath dataPath = syncEnumerator.Current;
          object item = m_dataSourceWrapper.DataHelper.ConvertBackItem( dataPath );
          object itemValue = selectedValuePathExtractor.GetValueFromItem( item );

          if( object.Equals( itemValue, selectedValue ) )
          {
            this.SelectedItem = item;
            return;
          }
        }

        // No matches were found.
        this.SelectedItem = null;
      }
      finally
      {
        m_updatingSelectedItemFromSelectedValue = false;
      }
    }

    #endregion

    #region SelectedValuePath Property

    public static readonly DependencyProperty SelectedValuePathProperty =
        DependencyProperty.Register( "SelectedValuePath", typeof( object ), typeof( ListBox ),
        new PropertyMetadata( null, new PropertyChangedCallback( ListBox.OnSelectedValuePathChanged ) ) );

    public string SelectedValuePath
    {
      get { return ( string )this.GetValue( ListBox.SelectedValuePathProperty ); }
      set { this.SetValue( ListBox.SelectedValuePathProperty, value ); }
    }

    private static void OnSelectedValuePathChanged( object sender, DependencyPropertyChangedEventArgs e )
    {
      ListBox listBox = ( ListBox )sender;

      listBox.m_cachedSelectedValuePath = e.NewValue as string;

      // Setting the BindingPathExtractor to null will make the next SelectedValuePathExtractor property getter
      // create a new instance for the new SelectedValuePath.
      listBox.m_selectedValuePathExtractor = null;


      // Refresh the SelectedVlaue
      listBox.UpdateSelectedValueFromSelectedItem();
    }

    #endregion

    #region ToolPaneVisibility

    public static readonly DependencyProperty ToolPaneVisibilityProperty = DependencyProperty.Register( "ToolPaneVisibility",
      typeof( Visibility ),
      typeof( ListBox ),
      new PropertyMetadata( Visibility.Visible ) );

    public Visibility ToolPaneVisibility
    {
      get
      {
        return ( Visibility )this.GetValue( ListBox.ToolPaneVisibilityProperty );
      }
      set
      {
        this.SetValue( ListBox.ToolPaneVisibilityProperty, value );
      }
    }

    #endregion

    #region HorizontalScrollBarVisibility Property

    public static readonly DependencyProperty HorizontalScrollBarVisibilityProperty = DependencyProperty.Register(
      "HorizontalScrollBarVisibility",
      typeof( ScrollBarVisibility ),
      typeof( ListBox ),
      new PropertyMetadata( ( ScrollBarVisibility )ScrollBarVisibility.Auto ) );

    public ScrollBarVisibility HorizontalScrollBarVisibility
    {
      get
      {
        return ( ScrollBarVisibility )this.GetValue( ListBox.HorizontalScrollBarVisibilityProperty );
      }
      set
      {
        this.SetValue( ListBox.HorizontalScrollBarVisibilityProperty, value );
      }
    }

    #endregion

    #region VerticalScrollBarVisibility Property

    public static readonly DependencyProperty VerticalScrollBarVisibilityProperty = DependencyProperty.Register(
      "VerticalScrollBarVisibility",
      typeof( ScrollBarVisibility ),
      typeof( ListBox ),
      new PropertyMetadata( ( ScrollBarVisibility )ScrollBarVisibility.Auto ) );

    public ScrollBarVisibility VerticalScrollBarVisibility
    {
      get
      {
        return ( ScrollBarVisibility )this.GetValue( ListBox.VerticalScrollBarVisibilityProperty );
      }
      set
      {
        this.SetValue( ListBox.VerticalScrollBarVisibilityProperty, value );
      }
    }

    #endregion

    #region NextViewCommand Property

    public ICommand NextViewCommand
    {
      get
      {
        if( m_nextView == null )
        {
          m_nextView = new Command( new Action<object>( this.ExecuteNextView ), new Func<object, bool>( this.CanExecuteNextView ) );
        }

        return m_nextView;
      }
    }

    #endregion

    #region PreviousViewCommand Property

    public ICommand PreviousViewCommand
    {
      get
      {
        if( m_previousView == null )
        {
          m_previousView = new Command( new Action<object>( this.ExecutePreviousView ), new Func<object, bool>( this.CanExecutePreviousView ) );
        }

        return m_previousView;
      }
    }

    #endregion

    #region IsLoaded Internal Property

    internal bool IsLoaded
    {
      get
      {
        return m_isLoaded;
      }
    }

    #endregion

    #region DataSourceWrapper Internal Property

    internal DataSourceWrapper DataSourceWrapper
    {
      get
      {
        if( m_dataSourceWrapper == null )
          this.UpdateDataSourceAndDataSourceEnumerator();

        return m_dataSourceWrapper;
      }
    }

    #endregion

    #region DataSourceEnumeratorWrapper Internal Property

    internal DataSourceEnumeratorWrapper DataSourceEnumeratorWrapper
    {
      get
      {
        if( m_dataSourceEnumeratorWrapper == null )
          this.UpdateDataSourceAndDataSourceEnumerator();

        if( !m_dataEnumeratorInitialized )
        {
          m_dataSourceEnumeratorWrapper.Reset();
          this.Panel.InitializeDataSourceEnumerator( m_dataSourceEnumeratorWrapper );
          m_dataEnumeratorInitialized = true;
        }

        return m_dataSourceEnumeratorWrapper;
      }
    }

    #endregion

    #region RequestsPending Internal Property

    internal bool RequestsPending
    {
      get
      {
        return m_pendingRequest;
      }
      set
      {
        if( m_pendingRequest != value )
        {
          m_pendingRequest = value;
          this.UpdateResquestStatusStates( true );
        }
      }
    }

    private bool m_pendingRequest = false;

    #endregion

    #region SelectionChanged Public Event

    public event EventHandler SelectionChanged;

    private void RaiseSelectionChanged()
    {
      if( this.SelectionChanged != null )
        this.SelectionChanged( this, EventArgs.Empty );
    }

    #endregion

    #region AllowDrag Public Property

    public static readonly DependencyProperty AllowDragProperty = DependencyProperty.Register(
      "AllowDrag",
      typeof( bool ),
      typeof( ListBox ),
      new PropertyMetadata( false ) );

    public bool AllowDrag
    {
      get
      {
        return ( bool )this.GetValue( ListBox.AllowDragProperty );
      }
      set
      {
        this.SetValue( ListBox.AllowDragProperty, value );
      }
    }

    #endregion

    #region DragStarting Public Event

    internal void OnDragStarting( DragDropCancelEventArgs e )
    {
      if( this.DragStarting != null )
        this.DragStarting( this, e );
    }

    public event EventHandler<DragDropCancelEventArgs> DragStarting;

    #endregion

    #region NavigateToGroupCommand Read-Only Property

    public ICommand NavigateToGroupCommand
    {
      get
      {
        if( m_navigateToGroupCommandCache == null )
        {
          m_navigateToGroupCommandCache = new Command(
            new Action<object>( this.OnNavigateToGroupExecuted ),
            new Func<object, bool>( this.OnNavigateToGroupCanExecute ) );
        }

        return m_navigateToGroupCommandCache;
      }
    }

    #endregion

    #region ShowDataNavigationPaneCommand Property
    public ICommand ShowDataNavigationPaneCommand
    {
      get
      {
        if( m_showDataNavigationPane == null )
        {
          m_showDataNavigationPane = new Command(
            new Action<object>( this.OnShowDataNavigationPaneExecute ),
            new Func<object, bool>( this.OnShowDataNavigationPaneCanExecute ) );
        }

        return m_showDataNavigationPane;
      }
    }
    #endregion

    #region HideDataNavigationPaneCommand Property
    public ICommand HideDataNavigationPaneCommand
    {
      get
      {
        if( m_hideDataNavigationPane == null )
        {
          m_hideDataNavigationPane = new Command(
            new Action<object>( this.OnHideDataNavigationPaneExecute ),
            new Func<object, bool>( this.OnHideDataNavigationPaneCanExecute ) );
        }

        return m_hideDataNavigationPane;
      }
    }
    #endregion

    #region ParentListBox Attached Property

    public static readonly DependencyProperty ParentListBoxProperty =
        DependencyProperty.RegisterAttached( "ParentListBox", typeof( ListBox ), typeof( ListBox ), new PropertyMetadata( null, ListBox.OnParentListBoxChanged ) );

    public static ListBox GetParentListBox( DependencyObject obj )
    {
      return ( ListBox )obj.GetValue( ListBox.ParentListBoxProperty );
    }

    internal static void SetParentListBox( DependencyObject obj, ListBox value )
    {
      obj.SetValue( ListBox.ParentListBoxProperty, value );
    }

    private static void OnParentListBoxChanged( DependencyObject sender, DependencyPropertyChangedEventArgs e )
    {
      PanelBase panel = sender as PanelBase;

      if( panel != null )
      {
        panel.OnParentListBoxChanged( sender as PanelBase, e );
      }
    }

    #endregion

    #region InternalAllowDrop Property

    private static readonly DependencyProperty InternalAllowDropProperty = DependencyProperty.Register(
      "InternalAllowDrop",
      typeof( bool ),
      typeof( ListBox ),
      new PropertyMetadata(
        ( bool )false,
        new PropertyChangedCallback( ListBox.OnInternalAllowDropChanged ) ) );

    private static void OnInternalAllowDropChanged( DependencyObject sender, DependencyPropertyChangedEventArgs e )
    {
      ListBox listBox = sender as ListBox;

      if( listBox == null )
        return;

      listBox.OnAllowDropChanged();
    }

    #endregion

    #region IsLocked Internal Property

    internal bool IsLocked
    {
      get;
      private set;
    }

    #endregion

    #region Public Methods

    public override void OnApplyTemplate()
    {
      base.OnApplyTemplate();

      ScrollViewer scrollViewer = this.GetTemplateChild( "ScrollViewer" ) as ScrollViewer;

      ItemsPresenter itemsPresenter = null;

      if( scrollViewer != null )
      {
        itemsPresenter = scrollViewer.Content as ItemsPresenter;
      }
      else
      {
        // In case no scrollViewer is defined in the ControlTemplate
        itemsPresenter = ListBox.TryGetItemsPresenter( this );
      }

      PanelBase panel = null;

      if( itemsPresenter != null )
      {
        itemsPresenter.InitializeAndLoadItemsPanelTemplate();

        panel = itemsPresenter.Content as PanelBase;
      }

      if( panel == null )
        throw new ListBoxException( "No ItemsPanel was specified or the ItemsPanel is not a type derived from RecyclingPanel." );

      ListBox.SetParentListBox( panel, this );

      this.Panel = panel;

      // Defer the first UpdateViewportContent until the ArrangeOverride 
      // since some properties are set during the MeasureOverride
      m_onApplyTemplateViewportUpdateDisposable = panel.LayoutManager.DeferUpdateViewport();

      this.UpdateDataSourceAndDataSourceEnumerator();
      this.ChangeVisualState( false );
    }

    public virtual DependencyObject GetContainerFromItem( object item )
    {
      if( item == null )
        throw new ArgumentNullException( "item" );

      PanelBase panel = this.Panel;

      if( panel == null )
        return null;

      return panel.GetContainerFromItem( item );
    }

    public void ScrollIntoView( object item )
    {
      if( item == null )
        throw new ArgumentNullException( "item" );

      PanelBase panel = this.Panel;

      if( panel == null )
        return;

      panel.BringItemIntoView( item );
    }

    public void Refresh()
    {
      m_forceRefresh = true;
      try
      {
        this.RefreshCore();
      }
      finally
      {
        m_forceRefresh = false;
      }
    }

    public void SelectAll()
    {
      m_selectionManager.SelectAllHelper();
    }

    public IAsyncResult BeginGetSelectedItems( AsyncCallback callback, object asyncState )
    {
      IList<SelectionRange> selectionRanges = this.SelectedRanges;

      // Prevent the selected items to be retrieved in design mode.
      if( m_isInDesigner )
      {
        selectionRanges = new SelectionRange[ 0 ];
      }

      SelectedItemsAsyncResult asyncResult = new SelectedItemsAsyncResult( selectionRanges, callback, asyncState, this.DataSourceWrapper );

      asyncResult.Launch();

      return asyncResult;
    }

    public IEnumerable<object> EndGetSelectedItems( IAsyncResult asyncResult )
    {
      if( asyncResult == null )
        throw new ArgumentNullException( "asyncResult" );

      SelectedItemsAsyncResult selectedItemsAsyncResult = asyncResult as SelectedItemsAsyncResult;

      if( selectedItemsAsyncResult == null )
        throw new ArgumentException( "The specified asyncResult must be a SelectedItemAsyncResult.", "asyncResult" );

      return selectedItemsAsyncResult.SelectedItems;
    }

    #endregion Public Methods

    #region Protected Methods

    internal void OnIsEnabledChanged( object sender, DependencyPropertyChangedEventArgs e )
    {
      this.UpdateCommonStates( true );
    }

    protected virtual bool OnShowDataNavigationPaneCanExecute( object parameter )
    {
      DataNavigationConfiguration dataNavigationConfiguration =
        this.GetValue( DataNavigationControl.DataNavigationConfigurationProperty ) as DataNavigationConfiguration;

      return ( dataNavigationConfiguration != null );
    }

    protected virtual void OnShowDataNavigationPaneExecute( object parameter )
    {
      m_dataNavigationPaneVisibility = Visibility.Visible;
      this.UpdateDataNavigationPaneStates( true );
    }

    protected virtual bool OnHideDataNavigationPaneCanExecute( object parameter )
    {
      return true;
    }

    protected virtual void OnHideDataNavigationPaneExecute( object parameter )
    {
      m_dataNavigationPaneVisibility = Visibility.Collapsed;
      this.UpdateDataNavigationPaneStates( true );
    }

    protected virtual bool OnNavigateToGroupCanExecute( object parameter )
    {
      if( ( this.GroupDescriptions.Count == 0 ) || ( parameter == null ) )
        return false;

      return true;
    }

    protected virtual void OnNavigateToGroupExecuted( object parameter )
    {
      this.NavigateToGroup( parameter );
    }

    protected override Size ArrangeOverride( Size finalSize )
    {
      if( m_onApplyTemplateViewportUpdateDisposable != null )
      {
        m_onApplyTemplateViewportUpdateDisposable.Dispose();
        m_onApplyTemplateViewportUpdateDisposable = null;
      }

      return base.ArrangeOverride( finalSize );
    }

    protected override void OnKeyDown( KeyEventArgs e )
    {
      base.OnKeyDown( e );

      if( ( ( Keyboard.Modifiers & ModifierKeys.Control ) == ModifierKeys.Control )
          && ( e.Key == Key.A ) )
      {
        this.SelectAll();
      }
    }

    protected override void OnGotFocus( RoutedEventArgs e )
    {
      base.OnGotFocus( e );

      if( e.OriginalSource != this )
        return;

      PanelBase panel = this.Panel;

      if( panel == null )
        return;

      FrameworkElement focusedElement = FocusManager.GetFocusedElement() as FrameworkElement;

      if( focusedElement == null )
        return;

      if( !focusedElement.IsDescendant( this ) )
        return;

      DataPath currentDataPath = this.CurrentDataPath;

      if( currentDataPath == null )
        return;

      ListBoxContainer currentContainer = panel.LayoutManager.GetContainerFromPath( currentDataPath );

      if( currentContainer == null )
        return;

      currentContainer.Focus();
    }

    protected internal virtual void PrepareContainerForItem( ListBoxContainer container, object item )
    {
      ListBox.SetParentListBox( container, this );

      DataPath itemDataPath = item as DataPath;

      if( itemDataPath == null )
      {
        Debug.Assert( false, "Create a DataPath from the item" );
      }

      container.DataPath = itemDataPath;
      container.SetDataContextFromDataPath( itemDataPath );

      SelectionRange itemSelectionRange = null;

      // If the DataPath is not loaded, an ItemReplace will be received
      // which will force another call to PrepareContainer for this DataPath
      // and ensure its selection state.
      if( container.IsSelectable && ( itemDataPath.LastChild.IsLoaded ) )
      {
        // Update the SelectionRange and IsSelected when preparing
        // a container to ensure its visual state is correctly set
        itemSelectionRange = SelectionHelper.FindSelectionRangeForPath( m_dataSourceWrapper,
          itemDataPath,
          this.SelectedRanges );
      }

      container.SelectionRange = itemSelectionRange;
      container.IsSelected = ( itemSelectionRange != null );

      Type containerType = container.GetType();
      ConfigurationType configurationType = ConfigurationType.Item;

      if( typeof( ListBoxItem ).IsAssignableFrom( containerType ) )
      {
        configurationType = ConfigurationType.Item;
      }
      else if( typeof( ListBoxGroupHeader ).IsAssignableFrom( containerType ) )
      {
        configurationType = ConfigurationType.GroupHeader;
      }
      else
      {
        throw new ListBoxInternalException( "Any other container configuration type is invalid." );
      }

      // Get a calculated configuration for the container type.
      ListBoxContainerConfigurationBase configuration = this.GetCachedContainerConfiguration( configurationType );

      // Set the ContainerLayoutMode used by the panel/active view to layout the container.
      container.PositioningLayoutMode = this.GetPositioningContainerLayoutMode();
      // Set the layout mode to the one used by the panel to layout the container.  This will be the default
      // if the container's configuration does not change it during the call to the configuration's PrepareContainerForItem.
      ListBoxContainer.SetLayoutMode( container, container.PositioningLayoutMode );

      // Apply the configuration.
      configuration.PrepareContainerForItem( container, item );

      container.IsDirty = false;
    }

    protected internal virtual void ClearContainerForItem( ListBoxContainer container )
    {
      container.ClearValue( ListBox.ParentListBoxProperty );

      container.DataContext = null;
      container.Content = null;
      container.DataPath = null;
      container.SelectionRange = null;
      container.IsSelected = false;
      container.IsMouseOver = false;
    }

    internal void Reset()
    {
      if( this.Panel != null )
        this.Panel.ResetPanel();

      // Ensure to use the UnselectAllHelper and not just
      // clearing the SelectedRanges in order to correctly
      // update the containers, SelectedItem and SelectedItems
      m_selectionManager.UnselectAllHelper();

      this.UpdateDataSourceAndDataSourceEnumerator();

      // Ensure to update the ItemsSource on the ItemsWrapper
      // collection in order to correctly support the Unbound
      // mode
      if( this.IsUnbound )
      {
        ItemsWrapper itemsWrapper = this.Items as ItemsWrapper;
        Debug.Assert( itemsWrapper != null );
        itemsWrapper.UpdateItemsSource();
      }
    }

    internal void OnItemTemplateChanged( DataTemplate dataTemplate, DataTemplate dataTemplate_2 )
    {
    }

    internal void OnOrientationChanged( Orientation oldValue, Orientation newValue )
    {
    }

    internal ListBoxContainerConfigurationBase GetContainerFactory( ConfigurationType configurationType )
    {
      return this.GetCachedContainerConfiguration( configurationType );
    }

    internal void OnListBoxContainerIsSelectedChanged( ListBoxContainer container )
    {
      if( ( container != null ) && ( container.IsSelected ) )
      {
        this.CurrentDataPath = container.DataPath;
      }
    }

    internal void ChangeVisualState( bool useTransitions )
    {
      this.UpdateLicensingStates( useTransitions );

      this.UpdateCommonStates( useTransitions );

      this.UpdateResquestStatusStates( useTransitions );

      this.UpdateDataNavigationPaneStates( useTransitions );
    }

    #endregion Protected Methods

    #region Internal Methods

    internal void UpdateSelection( ListBoxContainer container, SelectionAction action )
    {
      // Never update selection if a null container is updated or if its DataPath is not yet loaded
      if( ( container != null ) && ( container.DataPath != null ) && ( !container.DataPath.LastChild.IsLoaded ) )
        return;

      m_selectionManager.UpdateSelectionHelper( container, action, this.IsAsyncDataSource );
    }

    internal void ChangeVisualState()
    {
      this.ChangeVisualState( true );
    }

    internal bool GoToState( bool useTransitions, string stateName )
    {
      return VisualStateManager.GoToState( this, stateName, useTransitions );
    }

    internal void ClearDataSourceEnumeratorWrapper()
    {
      if( m_dataSourceEnumeratorWrapper != null )
      {
        m_dataSourceEnumeratorWrapper.DataChanged -= this.OnDataEnumerator_DataChanged;
        m_dataSourceEnumeratorWrapper.HasPendingRequestsChanged -= this.OnDataEnumerator_HasPendingRequestsChanged;
      }

      m_dataSourceEnumeratorWrapper = null;
    }

    internal void NavigateToNextGroup( object group )
    {
      this.NavigateToGroup( group, GroupTarget.Next );
    }

    internal void NavigateToPreviousGroup( object group )
    {
      this.NavigateToGroup( group, GroupTarget.Previous );
    }

    internal void NavigateToGroup( object group )
    {
      GroupDataContext groupDataContext = group as GroupDataContext;

      if( groupDataContext != null )
        group = groupDataContext.GroupValue;

      DataPath groupDataPath = this.CreateGroupDataPathFromGroupKey( group );

      if( groupDataPath == null )
        return;

      this.NavigateToGroup( GroupTarget.Current, groupDataPath );

      m_dataNavigationPaneVisibility = Visibility.Collapsed;
      this.UpdateDataNavigationPaneStates( true );

    }

    internal IDisposable DeferUpdateSelection()
    {
      return new DeferUpdateSelectedItemAndItems( this );
    }

    #endregion Internal Methods

    #region Private Properties

    private BindingPathValueExtractor SelectedValuePathExtractor
    {
      get
      {
        if( m_selectedValuePathExtractor == null )
        {
          m_selectedValuePathExtractor = new BindingPathValueExtractor( new PropertyPath( m_cachedSelectedValuePath ) );
        }

        return m_selectedValuePathExtractor;
      }
    }

    private BindingPathValueExtractor m_selectedValuePathExtractor;

    #endregion

    #region Private Methods

    private bool CanExecuteNextView( object parameter )
    {
      MultiViewPanel panel = this.Panel as MultiViewPanel;
      if( panel == null )
        return false;

      if( panel.Views.Count <= 1 )
        return false;

      return true;
    }

    private void ExecuteNextView( object parameter )
    {

      MultiViewPanel panel = this.Panel as MultiViewPanel;

      if( panel == null )
        return;

      ReadOnlyObservableCollection<View> panelViews = MultiViewPanel.GetPanelViews( this );

      if( panelViews == null )
        return;

      View view = MultiViewPanel.GetActiveView( this );
      int indexView = panelViews.IndexOf( view );

      indexView++;

      if( indexView == panelViews.Count )
        indexView = 0;

      panelViews[ indexView ].IsActiveView = true;
    }

    private bool CanExecutePreviousView( object parameter )
    {
      MultiViewPanel panel = this.Panel as MultiViewPanel;
      if( panel == null )
        return false;

      if( panel.Views.Count <= 1 )
        return false;

      return true;
    }

    private void ExecutePreviousView( object parameter )
    {
      MultiViewPanel panel = this.Panel as MultiViewPanel;

      if( panel == null )
        return;

      ReadOnlyObservableCollection<View> panelViews = MultiViewPanel.GetPanelViews( this );

      if( panelViews == null )
        return;

      View view = MultiViewPanel.GetActiveView( this );
      int indexView = panelViews.IndexOf( view );

      indexView--;

      if( indexView == -1 )
        indexView = panelViews.Count - 1;

      panelViews[ indexView ].IsActiveView = true;

    }

    private DataPath CreateGroupDataPathFromGroupKey( object group )
    {
      if( group == null )
        return null;

      Debug.Assert( this.GroupDescriptions.Count > 0 );

      GroupDataItem groupDataItem = new GroupDataItem( group );

      return new DataPath( groupDataItem );
    }

    private void NavigateToGroup( object parameter, GroupTarget groupTarget )
    {
      if( parameter == null )
        return;

      // Optimization to avoid the Panel to create the DataPath itself
      GroupDataContext groupDataContext = parameter as GroupDataContext;

      if( groupDataContext == null )
        return;

      DataPath groupDataPath = groupDataContext.DataPath;

      if( groupDataPath == null )
        return;

      this.NavigateToGroup( groupTarget, groupDataPath );

    }

    private void NavigateToGroup( GroupTarget target, DataPath groupDataPath )
    {
      if( this.Panel == null )
        throw new ListBoxInternalException( "Panel shouln't be null while navigating across groups." );

      DataSourceEnumeratorWrapper dataSourceEnumeratorWrapper = this.DataSourceEnumeratorWrapper;

      if( dataSourceEnumeratorWrapper == null )
        return;

      GroupFetchResult fetchResult = null;
      try
      {
        fetchResult = dataSourceEnumeratorWrapper.FetchGroup( groupDataPath, target, 1 );
      }
      catch( Exception exception )
      {
        throw new InvalidOperationException( "An error occurred trying to fetch a group. If using a DataNavigationConfiguration, " +
          "make sure that the data type of the added keys matches the first group description property's data type. See inner exception for details.",
          exception );
      }

      if( fetchResult == null )
      {
        Debug.Assert( false, "FetchGroup returned a null fetch result." );
        return;
      }

      if( fetchResult.EndPositionObject.IsAfterLast )
      {
        // If we reach the end of the data enumrator
        // Move the enumerator back and get the DataPath of the last valid group
        this.Panel.EnumeratorHelper.Move( -1 );

        DataPath currentDataPath = this.Panel.EnumeratorHelper.CurrentDataPath;
        DataPath validGroupPath = currentDataPath.CreateAncestorPath( groupDataPath.Depth );
        fetchResult = dataSourceEnumeratorWrapper.FetchGroup( validGroupPath, GroupTarget.Current, 1 );
      }

      ICollection<DataPath> fetchedItems = fetchResult.FetchedItems;

      if( ( fetchedItems != null ) && ( fetchedItems.Count > 0 ) )
      {
        this.Panel.JumpToPath( fetchedItems.First(), null, target, false );
      }
    }

    private ContainerLayoutMode GetPositioningContainerLayoutMode()
    {
      PanelBase panel = this.Panel;

      if( panel == null )
        throw new InvalidOperationException( "An invalid operation occurred. ItemsPanel is null." );

      ContainerLayoutMode containerLayoutMode = panel.GetPositioningContainerLayoutMode();

      if( containerLayoutMode == ContainerLayoutMode.Default )
        throw new InvalidOperationException( "An invalid operation occurred. The Panel's GetPositioningContainerLayoutMode should never return ContainerLayoutMode.Default." );

      return containerLayoutMode;
    }

    private void DeferRefresh()
    {
      // Only dispatch a call to Refresh if there is not already one dispatched.
      if( m_refreshDispatcherOperation == null )
      {
        m_refreshDispatcherOperation = this.Dispatcher.BeginInvoke( new Action( this.RefreshCore ) );
      }
    }

    private void RefreshCore()
    {
      if( ( !m_forceRefresh ) && ( m_refreshDispatcherOperation == null ) )
      {
        // A call to Refresh was called after the dispatching but before
        // the invoking.
        return;
      }

      PanelBase panel = this.Panel;

      if( panel != null )
      {
        panel.Refresh();
      }

      m_refreshDispatcherOperation = null;
    }

    private void OnSelectionManagerSelectionChanged( object sender, EventArgs e )
    {
      this.UpdateSelectedItemAndSelectedItems();
      this.RaiseSelectionChanged();
    }

    private void OnSelectionManagerUpdateSelectionProperties( object sender, EventArgs e )
    {
      if( this.Panel != null )
        this.Panel.UpdateSelectionPropertiesOnContainers();
    }

    private void OnDataEnumerator_HasPendingRequestsChanged( object sender, EventArgs e )
    {
      this.RequestsPending = !this.RequestsPending;
    }

    private void OnDataEnumerator_DataChanged( object sender, DataChangedEventArgs e )
    {
      if( this.Panel == null )
        return;

      this.Panel.OnDataEnumerator_DataChanged( sender, e );

      // Force an update of SelectedItemAndSelectedItems after a Replace or Remove
      // in the DataSource to ensure removed items are no longer present in the
      // SelectedItem or SelectedItems
      switch( e.Action )
      {
        case DataChangedAction.Replace:
        case DataChangedAction.Remove:
          this.Dispatcher.BeginInvoke( () =>
          {
            this.UpdateSelectedItemAndSelectedItems();
            this.RaiseSelectionChanged();
          } );
          break;
      }
    }

    private static ItemsPresenter TryGetItemsPresenter( FrameworkElement parent )
    {
      if( parent == null )
        return null;

      ItemsPresenter itemsPresenter = parent as ItemsPresenter;

      if( itemsPresenter != null )
        return itemsPresenter;

      int childrenCount = VisualTreeHelper.GetChildrenCount( parent );

      for( int i = 0; i < childrenCount; i++ )
      {
        FrameworkElement child = VisualTreeHelper.GetChild( parent, i ) as FrameworkElement;

        itemsPresenter = child as ItemsPresenter;

        if( itemsPresenter != null )
          return itemsPresenter;

        itemsPresenter = ListBox.TryGetItemsPresenter( child );

        if( itemsPresenter != null )
          return itemsPresenter;
      }

      return null;
    }

    private void UpdateDataSourceAndDataSourceEnumerator()
    {
      Debug.Assert( this.IsInitialized );

      // Get the ItemsSource or revert to 
      // Items if the ItemsSource is null
      // to be in "Unbound mode"
      // Note that when binding the ListBox ItemSource property, the ListBox can be
      // unbound for a short moment when EndInit is called but before the binding has resolved.
      object itemsSource = this.ItemsSource;

      if( itemsSource == null )
      {
        itemsSource = this.Items;
        this.IsUnbound = true;
      }
      else
      {
        this.IsUnbound = false;
      }

      if( m_cachedItemsSource != itemsSource )
      {
        try
        {
          Debug.Assert( itemsSource != null );

          m_dataSourceWrapper = DataSourceHelper.GenerateDataSourceForObject( itemsSource );

          Debug.Assert( m_dataSourceWrapper.Provider != null );

          this.IsAsyncDataSource = m_dataSourceWrapper.Provider.IsAsynchronous;

          m_cachedItemsSource = itemsSource;
        }
        catch( Exception exception )
        {
          throw new InvalidOperationException(
            "An internal error occurred generating the data source wrapper. " +
            "Make sure that the ItemsSource property is set to an IEnumerable instance or to an Xceed.Silverlight.Data.DataSourceProvider instance.",
            exception );
        }
      }

      // Never apply Grouping, Sorting, or Filtering to an unbound listbox.
      // Else the list is untyped and the data source wrapper won't be able to find
      // the properties corresponding to the grouping, sorting, or filtering criterias
      // resulting in a crash.
      if( !this.IsUnbound )
      {
        m_dataSourceWrapper.Filter = m_filter;
        m_dataSourceWrapper.FilterExpression = this.FilterExpression;

        this.UpdateDataSourceWrapperGroupDescriptions();
        this.UpdateDataSourceWrapperSortDescriptions();
      }

      this.ClearDataSourceEnumeratorWrapper();

      m_dataSourceEnumeratorWrapper = m_dataSourceWrapper.GetSyncDataEnumerator();
      m_dataSourceEnumeratorWrapper.DataChanged += this.OnDataEnumerator_DataChanged;
      m_dataSourceEnumeratorWrapper.HasPendingRequestsChanged += this.OnDataEnumerator_HasPendingRequestsChanged;

      if( m_dataSourceEnumeratorWrapper.IsCachingEnumerator )
      {
        m_dataSourceEnumeratorWrapper.SetCacheSize( 100 );
      }

      this.RequestsPending = false;
      m_dataEnumeratorInitialized = false;
    }

    private void UpdateDataSourceWrapperGroupDescriptions()
    {
      DataSourceWrapper dataSourceWrapper = this.DataSourceWrapper;

      // GroupDescriptions will be applied when the DataSourceWrapper
      // is initialized
      if( dataSourceWrapper == null )
        return;

      // Never apply Grouping, Sorting, or Filtering to an unbound listbox.
      // Else the list is untyped and the data source wrapper won't be able to find
      // the properties corresponding to the grouping, sorting, or filtering criterias
      // resulting in a crash.
      Debug.Assert( ( this.IsInitialized ) && ( !this.IsUnbound ) );

      ListBox.SyncCollections( this.GroupDescriptions, dataSourceWrapper.GroupDescriptions );
      this.ClearDataSourceEnumeratorWrapper();
    }

    private void UpdateDataSourceWrapperSortDescriptions()
    {
      DataSourceWrapper dataSourceWrapper = this.DataSourceWrapper;

      // SortDescriptions will be applied when the DataSourceWrapper
      // is initialized
      if( dataSourceWrapper == null )
        return;

      // Never apply Grouping, Sorting, or Filtering to an unbound listbox.
      // Else the list is untyped and the data source wrapper won't be able to find
      // the properties corresponding to the grouping, sorting, or filtering criterias
      // resulting in a crash.
      Debug.Assert( ( this.IsInitialized ) && ( !this.IsUnbound ) );

      ListBox.SyncCollections( this.SortDescriptions, dataSourceWrapper.SortDescriptions );
      this.ClearDataSourceEnumeratorWrapper();
    }

    private static void SyncCollections( IList listBoxCollection, IList dataSourceCollection )
    {
      if( dataSourceCollection == null )
        throw new ArgumentNullException( "dataSourceCollection" );

      if( listBoxCollection == null )
        throw new ArgumentNullException( "listBoxCollection" );

      dataSourceCollection.Clear();

      int listBoxCollectionCount = listBoxCollection.Count;

      for( int i = 0; i < listBoxCollectionCount; i++ )
      {
        dataSourceCollection.Add( listBoxCollection[ i ] );
      }
    }

    private void UpdateSelectedRangesFromSelectedItem()
    {
      DataSourceWrapper dataSourceWrapper = this.DataSourceWrapper;

      if( ( this.IsInitialized )
          && ( !this.IsUpdateSelectedItemsAndItemInhibited )
          && ( dataSourceWrapper != null ) )
      {
        object selectedItem = this.SelectedItem;

        if( selectedItem != null )
        {
          DataPath dataPath = dataSourceWrapper.CreateDataPathForObject( selectedItem );

          ListBoxContainer container = null;

          if( this.Panel != null )
            container = this.Panel.LayoutManager.GetContainerFromPath( dataPath, true );

          this.SelectionManager.UpdateSingleSelectionHelper( container, dataPath );
        }
        else
        {
          this.UpdateSelectedItemAndSelectedItems();
        }
      }
    }

    private void UpdateSelectedItemAndSelectedItems()
    {
      if( this.IsUpdateSelectedItemsAndItemInhibited )
        return;

      using( this.DeferUpdateSelection() )
      {
        IList<SelectionRange> selectedRanges = this.SelectedRanges;
        int selectedRangeCount = selectedRanges.Count;

        if( selectedRangeCount > 0 )
        {
          if( ( this.SelectionMode == SelectionMode.Single ) && ( selectedRangeCount > 1 ) )
            throw new ListBoxInternalException( "More than one range selected while in SelectionMode.Single." );

          // Always treat the first item of the first selectionRange as the SelectedItem
          SelectionRange range = selectedRanges[ 0 ];

          if( ( this.SelectionMode == SelectionMode.Single ) && !range.IsSingleItem )
            throw new ListBoxInternalException( "A range with multiple items is selected while in SelectionMode.Single." );

          // If the update was completed synchronously, the update is completed
          // else, wait for the operation to complete and process
          // everything in the callback
          if( !m_selectedItems.UpdateSelectedItems() )
            return;
        }
        else
        {
          ( ( IList )m_selectedItems ).Clear();
        }

        this.UpdateSelectedItem();
      }
    }

    private void OnSelectedItemsCompleted( object sender, EventArgs e )
    {
      this.UpdateSelectedItem();
    }

    private void UpdateSelectedItem()
    {
      IList selectedItemsIList = m_selectedItems as IList;

      this.SelectedItem = ( selectedItemsIList.Count > 0 )
       ? selectedItemsIList[ 0 ]
       : null;
    }

    private void UpdateLicensingStates( bool useTransitions )
    {
      if( m_isLicensed )
      {
        VisualStateManager.GoToState( this, "Licensed", useTransitions );
      }
      else
      {
        VisualStateManager.GoToState( this, "NotLicensed", useTransitions );
      }
    }

    private void UpdateCommonStates( bool useTransitions )
    {
      if( !base.IsEnabled )
      {
        this.GoToState( useTransitions, "Disabled" );
      }
      else
      {
        this.GoToState( useTransitions, "Normal" );
      }
    }

    private void UpdateResquestStatusStates( bool useTransitions )
    {
      if( m_pendingRequest )
      {
        this.GoToState( useTransitions, "RequestsPending" );
      }
      else
      {
        this.GoToState( useTransitions, "NoPendingRequests" );
      }
    }

    private void UpdateDataNavigationPaneStates( bool useTransition )
    {
      if( m_dataNavigationPaneVisibility == Visibility.Visible )
      {
        this.GoToState( useTransition, "DataNavigationPaneVisible" );
      }
      else
      {
        this.GoToState( useTransition, "DataNavigationPaneHidden" );
      }

    }
    #endregion Private Methods

    #region Private Event Handlers

    private void OnLoaded( object sender, RoutedEventArgs e )
    {
#if !WINDOWS_PHONE
      if( this.AllowDrop )
        DragDropManager.AddDropTarget( this );
#endif

      m_isLoaded = true;
      this.ChangeVisualState( false );
    }

    private void OnUnLoaded( object sender, RoutedEventArgs e )
    {
      // We call RemoveDropTarget wether or not AllowDrop is set to true or false
      // in case that the property value was changed from true to false before the control
      // was unloaded.
      DragDropManager.RemoveDropTarget( this );

      m_isLoaded = false;
      this.ChangeVisualState( false );
    }

    private void OnAllowDropChanged()
    {
#if !WINDOWS_PHONE
      if( this.AllowDrop )
      {
        DragDropManager.AddDropTarget( this );
      }
      else
      {
        DragDropManager.RemoveDropTarget( this );
      }
#endif
    }

    #endregion Private Event Handlers

    #region Private Nested Class
    private class DeferUpdateSelectedItemAndItems : IDisposable
    {
      public DeferUpdateSelectedItemAndItems( ListBox listBox )
      {
        if( listBox == null )
          throw new NullReferenceException( "ListBox should not be null." );

        m_listBox = listBox;
        m_listBox.m_inhibitUpdateSelectedItemAndItemsCount++;
      }

      public void Dispose()
      {
        m_listBox.m_inhibitUpdateSelectedItemAndItemsCount--;
        m_listBox = null;
      }

      private ListBox m_listBox;
    }
    #endregion

    #region Private Fields

    private Predicate<object> m_filter;
    private bool m_isLoaded;

    private DispatcherOperation m_refreshDispatcherOperation;
    internal bool m_dataEnumeratorInitialized;
    private bool m_forceRefresh;
    private int m_inhibitUpdateSelectedItemAndItemsCount;
    private int m_iSupportInitializeCount;

    private Visibility m_dataNavigationPaneVisibility = Visibility.Collapsed;

    private SelectionManager m_selectionManager;
    private DataSourceWrapper m_dataSourceWrapper;
    private DataSourceEnumeratorWrapper m_dataSourceEnumeratorWrapper;
    private SelectedItemsWrapper m_selectedItems;
    private ItemsWrapper m_items;
    private object m_cachedItemsSource;

    private string m_cachedSelectedValuePath;
    private bool m_updatingSelectedItemFromSelectedValue;
    private bool m_updatingSelectedValueFromSelectedItem;

    private IDisposable m_onApplyTemplateViewportUpdateDisposable;
    private Command m_navigateToGroupCommandCache;

    private Command m_showDataNavigationPane;
    private Command m_hideDataNavigationPane;

    private Command m_nextView;
    private Command m_previousView;

    //licensing
    private readonly Guid m_designTimeGuid;
    private readonly bool m_isInDesigner;
    private bool m_isLicensed = true;

    #endregion

    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;

    internal void RaisePropertyChanged( string propertyName )
    {
      if( this.PropertyChanged == null )
        return;

      if( propertyName == null )
        propertyName = string.Empty;

      this.PropertyChanged( this, new PropertyChangedEventArgs( propertyName ) );
    }

    #endregion

    #region ISupportInitialize Members

    internal bool IsInitialized
    {
      get
      {
        return ( m_iSupportInitializeCount == 0 );
      }
    }

    void ISupportInitialize.BeginInit()
    {
      m_iSupportInitializeCount++;

      if( m_iSupportInitializeCount == 1 )
      {
        this.BeginInitCore();
      }
    }

    void ISupportInitialize.EndInit()
    {
      if( m_iSupportInitializeCount == 0 )
        return;

      m_iSupportInitializeCount--;

      if( m_iSupportInitializeCount == 0 )
      {
        this.EndInitCore();
      }
    }

    internal void BeginInitCore()
    {
    }

    internal void EndInitCore()
    {
      this.UpdateDataSourceAndDataSourceEnumerator();

      // We must update the Selection Ranges
      // if a SelectedItem was set
      if( this.SelectedItem != null )
      {
        this.UpdateSelectedRangesFromSelectedItem();
      }
      // Or if SelectedItems were defined
      else if( this.SelectedItems.Count > 0 )
      {
        this.UpdateSelectedItemAndSelectedItems();
      }
    }

    #endregion

    #region IWeakEventListener Members

    bool IWeakEventListener.ReceiveWeakEvent( Type managerType, object sender, EventArgs e )
    {
      bool handled = false;

      if( managerType == typeof( ContainerConfigurationsInvalidatedManager ) )
      {
        ConfigurationChangedEventArgs configurationChangedEventArgs = ( ConfigurationChangedEventArgs )e;

        ConfigurationType configurationType = configurationChangedEventArgs.ConfigurationType;

        if( configurationType == ConfigurationType.All )
        {
          // No container type specified, clear all configurations.
          this.ClearContainerConfigurationCache();
        }
        else
        {
          // Clear only the cached configuration for container type.
          this.InvalidateContainerConfigurationCache( configurationType );
        }

        handled = true;
      }
      else if( managerType == typeof( CachedSizeChangedManager ) )
      {
        CachedSizeChangedEventArgs sizeChangedEventArgs = ( CachedSizeChangedEventArgs )e;

        ContainerLayoutMode positioningContainerLayoutMode = this.GetPositioningContainerLayoutMode();
        if( sizeChangedEventArgs.ShouldInvalidateLayout( positioningContainerLayoutMode ) )
        {
          this.DeferRefresh();
        }
      }

      return handled;
    }

    #endregion

    #region IGroupNavigationHost Members

    DataNavigationConfiguration IDataNavigationHost.Configuration
    {
      get
      {
        return DataNavigationControl.GetDataNavigationConfiguration( this );
      }
    }

    void IDataNavigationHost.NavigateToKey( object group )
    {
      this.OnNavigateToGroupExecuted( group );
    }

    ICommand IDataNavigationHost.NavigateToKeyCommand
    {
      get
      {
        return this.NavigateToGroupCommand;
      }
    }

    #endregion

    #region IDropTarget Members

    bool IDropTarget.CanDropElement( DragDropContext dragDropContext )
    {
      return this.OnIDropTargetCanDropElement( dragDropContext );
    }

    void IDropTarget.DragEnter( DragDropContext dragDropContext )
    {
      this.OnIDropTargetDragEnter( dragDropContext );
    }

    void IDropTarget.DragOver( DragDropContext dragDropContext, Point mousePosition )
    {
      this.OnIDropTargetDragOver( dragDropContext, mousePosition );
    }

    void IDropTarget.DragLeave( DragDropContext dragDropContext )
    {
      this.OnIDropTargetDragLeave( dragDropContext );
    }

    void IDropTarget.Drop( DragDropContext dragDropContext )
    {
      this.OnIDropTargetDrop( dragDropContext );
    }

    internal bool OnIDropTargetCanDropElement( DragDropContext dragDropContext )
    {
      if( this.IDropTargetCanDropElement != null )
        return this.IDropTargetCanDropElement( this, new DropEventArgs( dragDropContext ) );

      return false;
    }

    internal void OnIDropTargetDragEnter( DragDropContext dragDropContext )
    {
      if( this.IDropTargetDragEnter != null )
        this.IDropTargetDragEnter( this, new DropEventArgs( dragDropContext ) );
    }

    internal void OnIDropTargetDragOver( DragDropContext dragDropContext, Point mousePosition )
    {
      if( this.IDropTargetDragOver != null )
        this.IDropTargetDragOver( this, new DropEventArgs( dragDropContext ) );
    }

    internal void OnIDropTargetDragLeave( DragDropContext dragDropContext )
    {
      if( this.IDropTargetDragLeave != null )
        this.IDropTargetDragLeave( this, new DropEventArgs( dragDropContext ) );
    }

    internal void OnIDropTargetDrop( DragDropContext dragDropContext )
    {
      if( this.IDropTargetDrop != null )
        this.IDropTargetDrop( this, new DropEventArgs( dragDropContext ) );
    }

    public event CanDropEventHandler IDropTargetCanDropElement;

    public event EventHandler<DropEventArgs> IDropTargetDragEnter;

    public event EventHandler<DropEventArgs> IDropTargetDragOver;

    public event EventHandler<DropEventArgs> IDropTargetDragLeave;

    public event EventHandler<DropEventArgs> IDropTargetDrop;

    #endregion

  }
}
